// License Notification
//======================================================
// Copyright (c) 2010 Channel Intelligence Inc.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// ======================================================




using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.Reflection;
using CIP4.Common;
using CIP4.Item;

namespace CIP4.Serializer
{
    public class GeneratorBuilderType
    {
        public static void Build(SerializerKindEnum serializerKind, string assemblyFileName, string serializerLocation, LoggerDelegate logger)
        {
            Build(serializerKind, new Dictionary<string, TypeContextType>(), assemblyFileName, serializerLocation, logger);
        }

        private static void Build(SerializerKindEnum serializerKind, Dictionary<string, TypeContextType> alreadyDone, string assemblyFileName, string serializerLocation, LoggerDelegate logger)
        {
            if (string.IsNullOrEmpty(assemblyFileName))
                throw new ApplicationException("Unexpected null assemblyFileName");
            DirectoryInfo dir = new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.System));
            if (dir.Parent == null)
                throw new ApplicationException("Unexpected absence of parent folder for system folder...");
            string windowsPath = dir.Parent.FullName;
            string fileNameString = Path.GetFileNameWithoutExtension(assemblyFileName);
            if (string.IsNullOrEmpty(fileNameString))
                throw new ApplicationException(string.Format("Given assembly file name does not have a anem: {0}", assemblyFileName));
            string serializerName =
                Path.Combine(serializerLocation, fileNameString);
            string references = string.Empty;
            List<string> assemblyReferences = new List<string>();
            assemblyReferences.Add(string.Format(@"{0}\CIP4.Core.dll", Globals.CIP4Bin));
            assemblyReferences.Add(string.Format(@"{0}\CIP4.Serializer.dll", Globals.CIP4Bin));
            assemblyReferences.Add(string.Format(@"{0}\CIP4.Simple.dll", Globals.CIP4Bin));
            assemblyReferences.Add(string.Format(@"{0}\MongoDB.Bson.dll", Globals.CIP4Bin));
            assemblyReferences.Add(string.Format(@"{0}\MongoDB.Driver.dll", Globals.CIP4Bin));
            //assemblyReferences.Add(string.Format(@"{0}\MongoDB.GridFS.dll", Globals.CIP4Bin));

            // There are types in Serializer and Simple that can be stored in CIPl collections - 
            // so AssemblyName could already be in assemblyReferences - don't add it again...
            if (!assemblyReferences.Any(p => string.Compare(Path.GetFileName(p), Path.GetFileName(assemblyFileName), true) == 0))
                assemblyReferences.Add(assemblyFileName);

            foreach(string referencedAssemplyName in assemblyReferences)
                references += " /reference:\"" + referencedAssemplyName + "\"";

            foreach (TypeContextType t in alreadyDone.Values)
            {
                TypeContextType t1 = t;
                if (t.TheType.Assembly.Location.Contains("mscorlib.dll") || assemblyReferences.Any(p => string.Compare(Path.GetFileName(p), Path.GetFileName(t1.TheType.Assembly.Location), true) == 0))
                    continue;
                assemblyReferences.Add(t.TheType.Assembly.Location);
                references += " /reference:\"" + t.TheType.Assembly.Location + "\"";
            }
            foreach (TypeContextType t in alreadyDone.Values)
            {
                foreach (Type intType in t.TheType.GetInterfaces())
                {
                    Type type = intType;
                    if (intType.Assembly.Location.Contains("mscorlib.dll") || assemblyReferences.Any(p => string.Compare(Path.GetFileName(p), Path.GetFileName(type.Assembly.Location), true) == 0))
                        continue;
                    assemblyReferences.Add(intType.Assembly.Location);
                    references += " /reference:\"" + intType.Assembly.Location + "\"";
                }
            }

            string csc64RefsCmd = string.Format(
                @"/reference:""{1}\Reference Assemblies\Microsoft\Framework\v3.5\System.Core.dll""  " +
                @"/reference:""{0}\Microsoft.NET\Framework64\v2.0.50727\System.dll""  " +
                @"/reference:""{0}\Microsoft.NET\Framework64\v2.0.50727\System.XML.dll""  " +
                @"/reference:""{0}\Microsoft.NET\Framework64\v2.0.50727\System.Drawing.dll"" " +
                @"/reference:""{1}\Reference Assemblies\Microsoft\Framework\v3.5\System.Xml.Linq.dll"" ",
                windowsPath,                                                        // {0}
                Globals.ProgramFilesDirectory);                                     // {1}

            string csc32RefsCmd = string.Format(
                @"/reference:""{1}\Reference Assemblies\Microsoft\Framework\v3.5\System.Core.dll"" " +
                @"/reference:""{0}\Microsoft.NET\Framework\v2.0.50727\System.dll"" " +
                @"/reference:""{0}\Microsoft.NET\Framework\v2.0.50727\System.XML.dll"" " +
                @"/reference:""{0}\Microsoft.NET\Framework\v2.0.50727\System.Drawing.dll"" " +
                @"/reference:""{1}\Reference Assemblies\Microsoft\Framework\v3.5\System.Xml.Linq.dll"" ",
                windowsPath,                                                        // {0}
                Globals.ProgramFilesDirectory);                                     // {1}

            string cscCmd = string.Format(
                @"/noconfig /nowarn:1701,1702 /errorreport:prompt /warn:4 /define:DEBUG;TRACE " +
                @"{3}" +
                @"{1} " +
                @"/debug+ /debug:full /filealign:512 /optimize- " +
                @"/out:""{0}_{2}Serializer.dll"" " +
                @"/target:library ""{0}_{2}serializer.cs""",
                serializerName,                                                     // {0}
                references,                                                         // {1}
                serializerKind,                                                     // {2} 
                (IntPtr.Size == 8) ? csc64RefsCmd : csc32RefsCmd);                  // {3}
                // Oh boy, half of this stuff isn't used - I daren't clean it up... Might be useful someday

            if (cscCmd.Contains("mscorlib.dll"))
                throw new ApplicationException(string.Format("mscorlib should not be included in the csc references: {0}", cscCmd));


            DataReceivedEventHandler outHandler = delegate(object sender, DataReceivedEventArgs e)
            {
                if (!string.IsNullOrEmpty(e.Data))
                {
                    if (e.Data.Contains(": error "))
                        logger(MajorEnum.Error, MinorEnum.Generic, "\n{0}", new object[] { e.Data });
                    else
                        logger(MajorEnum.Trace, MinorEnum.Generic, "\n{0}", new object[] { e.Data });
                }
            };
            DataReceivedEventHandler errHandler = delegate(object sender, DataReceivedEventArgs e)
            {
                if (!string.IsNullOrEmpty(e.Data))
                    logger(MajorEnum.Error, MinorEnum.Generic, "\n{0}", new object[] { e.Data });
            };
            EventHandler exitedHandler = delegate(object sender, EventArgs e)
            {
                logger(MajorEnum.Trace, MinorEnum.Generic, "\n{0} exited", new object[] { sender.ToString() });
            };

            logger(MajorEnum.Trace, MinorEnum.Generic, "Start compiling {0}_{1}Serializer.cs", new object[] { serializerName, serializerKind });

            RunCommand(
                string.Format(@"{0}\Microsoft.NET\Framework\v3.5\Csc.exe", windowsPath),
                cscCmd,
                Path.GetDirectoryName(assemblyFileName),
                outHandler,
                errHandler,
                exitedHandler,
                logger);

            logger(MajorEnum.Trace, MinorEnum.Generic, "Finished compiling {0}_{1}Serializer.dll", new object[] { serializerName, serializerKind });

        }

        public static bool RunCommand(
            string exe,
            string cmd,
            string workingDirectory,
            DataReceivedEventHandler outData,
            DataReceivedEventHandler errorData,
            EventHandler procExited,
            LoggerDelegate logger)
        {
            try
            {
                logger(MajorEnum.Trace, MinorEnum.Generic, "workingDirectory {0}\n{1} {2}", new object[] { workingDirectory, exe, cmd });

                ProcessStartInfo processInfo = 
                    new ProcessStartInfo
                    {
                        Verb = "runas",
                        Arguments = cmd,
                        FileName = exe,
                        WorkingDirectory = workingDirectory,
                        RedirectStandardOutput = true,
                        RedirectStandardError = true,
                        UseShellExecute = false,
                        CreateNoWindow = true
                    };
                logger(MajorEnum.Trace, MinorEnum.Generic, "-- {0} started doing {1}", new object[] { exe, cmd });
                Process proc = Process.Start(processInfo);
                proc.OutputDataReceived += outData;
                proc.ErrorDataReceived += errorData;
                proc.Exited += procExited;
                proc.BeginOutputReadLine();
                proc.BeginErrorReadLine();
                int seconds = 0;
                while (!proc.WaitForExit(1000))
                    logger(MajorEnum.Noise, MinorEnum.Generic, "-- waited {0} secs for exe to finished doing {1}", new object[] { ++seconds, cmd });
                proc.CancelOutputRead();
                proc.CancelErrorRead();
                proc.Dispose();
                //Logger(MajorEnum.Information, MinorEnum.Generic, "-- csc.exe finished doing {0}", new object[] { cmd });
            }
            catch (Exception ex)
            {
                logger(MajorEnum.Error, MinorEnum.Generic, "Error running csc: {0}", new object[] { ex });
                return false;
            }
            return true;
        }

        public static bool GenerateAndBuild(SerializerKindEnum serializerKind, string assemblyFileName, string serializerLocation, List<string> typeNames, LoggerDelegate logger)
        {
            if (!Globals.IsAssemblyResolverSetup)
                return false;
            Globals.AddAssemblyPath(Path.GetDirectoryName(assemblyFileName), logger);
            Dictionary<string, TypeContextType> alreadyDone = new Dictionary<string, TypeContextType>();
            if (Generate(serializerKind, assemblyFileName, serializerLocation, typeNames, alreadyDone, logger))
                Build(serializerKind, alreadyDone, assemblyFileName, serializerLocation, logger);
            else
                return false;
            return true;
        }

        public static bool Generate(SerializerKindEnum serializerKind, string assemblyFileName, string serializerLocation, List<string> typeNames, LoggerDelegate logger)
        {
            return Generate(serializerKind, assemblyFileName, serializerLocation, typeNames, new Dictionary<string, TypeContextType>(), logger);
        }

        private static bool Generate(SerializerKindEnum serializerKind, string assemblyFileName, string serializerLocation, List<string> typeNames, Dictionary<string, TypeContextType> alreadyDone, LoggerDelegate logger)
        {
            bool result = true;
            if (Directory.Exists(assemblyFileName)) // just to be nice - otherwise the LoadFile throw an "Access Denied" exception
                throw new ArgumentException("You must provide a file not a directory - {0}", assemblyFileName);
            Assembly ass = Assembly.LoadFile(assemblyFileName);
            string text = string.Empty;

            // very, very ugly the SerializerBaseTypeBody (etc) accumulates text for all the 
            // types in the assembly, it must be cleared out here so text from some previous 
            // assembly doesn't turn up in the serializer we're about to try and build.
            XmlSerializerBuilderType.SerializerBaseTypeBody = string.Empty;
            XmlSerializerBuilderType.DeSerializerBaseTypeBody = string.Empty;
            XmlSerializerBuilderType.FirstNamespaceName = string.Empty;
            JsonSerializerBuilderType.SerializerBaseTypeBody = string.Empty;
            JsonSerializerBuilderType.DeSerializerBaseTypeBody = string.Empty;
            JsonSerializerBuilderType.FirstNamespaceName = string.Empty;
            
            if (typeNames.Count > 0)
            {
                foreach (string typeName in typeNames)
                {
                    Type t = ass.GetType(typeName);
                    if (t == null)
                    {
                        logger(MajorEnum.Error, MinorEnum.SerializationError, "Unable to find type {0} in {1}", new object[] { typeName, assemblyFileName});
                        result = false;
                    }
                    else
                    {
                        if (serializerKind == SerializerKindEnum.CIP4Json)
                        {
                            JsonSerializerBuilderType builder = new JsonSerializerBuilderType(new TypeContextType { OriginalType = t });
                            text += builder.GetSerializer(alreadyDone);
                        }
                        else
                        {
                            XmlSerializerBuilderType builder = new XmlSerializerBuilderType(new TypeContextType { OriginalType = t });
                            text += builder.GetSerializer(alreadyDone);
                        }
                    }
                }
            }
            else
            {
                foreach (Type t in ass.GetTypes())
                {
                    CIP4ItemAttribute itemAttribute = CIP4ItemAttribute.GetAttribute(t);
                    if (itemAttribute != null)
                    {
                        if (serializerKind == SerializerKindEnum.CIP4Json)
                        {
                            JsonSerializerBuilderType builder = new JsonSerializerBuilderType(new TypeContextType { OriginalType = t });
                            text += builder.GetSerializer(alreadyDone);
                        }
                        else
                        {
                            XmlSerializerBuilderType builder = new XmlSerializerBuilderType(new TypeContextType { OriginalType = t });
                            text += builder.GetSerializer(alreadyDone);
                        }
                    }
                }
                if (string.IsNullOrEmpty(text))
                {
                    logger(MajorEnum.Warning, MinorEnum.Generic, "No item types were found in {0} - if you want to generate a serializer for an item that does not have a CIP4ItemAttribute you must give CIP4.CMD the type name using the /TypeName argument", new object[] { assemblyFileName });
                    return false;
                }
            }

            if (serializerKind == SerializerKindEnum.CIP4Xml)
                text = XmlSerializerBuilderType.GetSerializerBaseType() + text;
            else
                text = JsonSerializerBuilderType.GetSerializerBaseType() + text;

            StreamWriter wrtr = new StreamWriter(Path.Combine(serializerLocation,
                Path.GetFileNameWithoutExtension(assemblyFileName) + string.Format("_{0}serializer.cs", serializerKind)));

            wrtr.WriteLine(string.Format(CSharpHeaderLiteral, text));

            wrtr.Close();

            string listText = string.Empty;
            foreach (TypeContextType t in alreadyDone.Values)
                listText += listText.Seperator(", ") + t.TheType.Name;

            logger(MajorEnum.Trace, MinorEnum.Generic, "Serializer generated for {0}",
                new object[] { listText });

            return result;

        }

        private const string CSharpHeaderLiteral =
            @"
// License Notification
//======================================================
// Copyright (c) 2010 Channel Intelligence Inc.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// ======================================================


using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using System.Reflection;
using MongoDB.Bson;
using CIP4.Common;
using CIP4.Simple;
using CIP4.Item;

namespace CIP4.Serializer
{{

{0}

}}
";
    }
}
